passport를 사용한 로그인과 세션

✒️ 2025-05-26 14:07 내용 수정

Node.js 교과서 개정 3판 내용 정리


passport를 사용한 로그인과 세션


로그인 설정

  1. 먼저 필요한 라이브러리를 VSC 터미널로 설치한다.
npm i express express-session express-mysql-session passport passport-local bcrypt
  1. server.js에서 필요한 라이브러리를 모두 importgkrh, express 설정을 먼저 진행한다.
// 1. 모듈 - require
const express = require('express');
const app = express();

const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local');

// 2. use, set - 등록
app.set('view engine', 'ejs');
app.use(express.static(__dirname + '/public'));

app.use(express.json());
app.use(express.urlencoded({extended : true}));
  1. sequelize를 통한 db 설정 및 객체를 가져온다.
// db
const db = require('./models');
const { User } = db;
  1. 세션을 저장할 DB 설정을 진행한다.
const MySQLStore = require("express-mysql-session")(session);

// MySQLStore 옵션
// 세션을 저장할 DB 옵션을 설정
const dbOption = {
  host: '127.0.0.1',
  port: '3306',
  user: 'root',
  password: 'password',
  database: 'databaseName',
}
  1. passport 설정을 진행한다.
    • passport.initialize(), app.use(session()), app.use(passport.session()) 메소드 순서는 반드시 지켜야 한다.
    • passport.serializeUser() : 로그인 시 실행되며, req.session() 객체에 저장할 데이터를 결정한다.
    • passport.deserializeUser() : 매 요청마다 실행되며, passport.serializeUser()를 통해 저장된 req.session() 객체 내에 아이디 혹은 이메일 등의 정보를 가져와 DB에서 사용자의 정보를 조회하고, 조회한 정보를 req.user에 저장한다.
      • deserializeUser()를 통해 세션에 불필요한 정보를 담아두지 않아도 되며, 로그인 후엔 사용자의 정보를 req.user에서 가져와서 사용할 수 있다.
// 세션을 사용하기 위한 옵션 설정
app.use(session({
  secret: '4154@#$%&*(6586/*163',
  resave : false,
  saveUninitialized : false,
  cookie : {maxAge : 60 * 60 * 1000},
  store : new MySQLStore( dbOption ), // 세션 저장위치
}));

// 초기화, 사용자 인증 요청 처리할수 있도록 함
// app.use(session)보다 뒤에 와야 함
app.use(passport.initialize());

// 세션 사용하도록 설정
app.use(passport.session());

// 로그인 검증 - 검증 방법
passport.use(new LocalStrategy(async (username, pw, done) => {
  // 사용자 정보가 DB에 존재하는지 조회
  let result = await User.findOne({ where : { username : username}});
  
  if (!result) { // 사용자 정보가 없을 시
    return done(null, false, { message: '아이디 DB에 없음' });
  }

  // 비밀번호를 암호화 후 비교
  let validPw = await bcrypt.compare(pw, result.password)
  if (!validPw) {
    return done(null, false, { message: '비밀번호 불일치' });
  }

  // DB에 정보가 있고 비밀번호도 일치한다면 통과
  return done(null, result);
}));

// 로그인 시 실행
// req.session 객체에 데이터를 저장해준다.
passport.serializeUser( (user, done) =>{
  process.nextTick(() => {    
	// 첫 번째는 에러, 두 번째는 저장할 데이터를 전달
	// 사용자의 id와 이름만 저장
    done(null, { id: user.id, username: user.username });
  });
});

// 매 요청 때마다 실행
// serializeUser에서 저장한 데이터가 deserializeUser의 매개변수
// serializeUser()에서 사용자 정보 객체의 id만 저장하고, 
// deserializeUser에서 해당 id로 사용자 정보 객체를 불러온다.
passport.deserializeUser( async (user, done) => {
  // 사용자의 정보를 조회
  let result = await User.findOne({where : {id : user.id}})
  
  if(result) { // 정보가 있다면 객체에 정보 저장
    const newUserInfo={
      id : result.id,
      username : result.username
    }

    process.nextTick(() => {
      return done(null, newUserInfo); // req.user에 newUserInfo를 저장
    });
  }
});
  1. 로그인과 연관된 라우터를 설정한다.
    • login 라우터에서 passport.authenticate()를 호출하면 LocalStrategy를 수행하고, 검증이 완료되면 req.login을 호출해 로그인을 진행한다.
// 3. listen - 포트번호 지정
let port = 8081;
app.listen(port , ()=>{
  console.log('접속 성공! - http://localhost:'+port)
});


// 4. 라우팅
app.get('/', async (req, res)=>{
  res.redirect('/login');
});

// 로그인 페이지로 이동
app.get('/login', (req, res)=>{
  res.render('login.ejs');
});

// 로그인 실패 시 처리
app.get('/login/:state', (req, res)=>{
  let {state} = req.params
  if(state== 'fail'){
    res.render('login-fail.ejs');
  }
});

// 로그인
app.post('/login', (req, res)=>{
  // passport.authenticate()가 호출되면 LocalStrategy를 수행
  passport.authenticate('local', (error, user, info)=>{ // 만약 외부 api로 로그인 한다면 'local'을 변경
    // DB 에러
    if (error) return res.status(500).json(error);
    // DB에 등록된 계정이 없음
    if (!user) return res.redirect('/login/fail');
    
    // 로그인
    req.logIn(user, (err) => {  
      if (err) return next(err);
      res.redirect('/main');
    })
  })(req,res);

});

// 메인 : 로그인 후 메인 페이지
app.get('/main', async (req, res) => {

  if (req.isAuthenticated()) { // 사용자가 로그인 한 상태인지 확인하는 메소드
      const userInfo = req.user; // req.user에서 사용자 정보를 가져올 수 있다.
      const username = userInfo.username;
      const userId = userInfo.id;
      try {
          res.render('main.ejs');
      } catch (error) {
          console.log(error);
          res.status(500).send('서버 DB 연결 에러!');
      }
  } else {
      res.redirect("/login");
  }
});

회원 가입

// 회원가입
app.get("/join", async function(req, res) {
  res.render("join.ejs", {isJoined : {message : ''}});
})

app.post("/join", async function(req, res) {
  
  const newUser = { // 회원가입 요청 정보
    username : req.body.username,
    password : await bcrypt.hash(req.body.password, 12)
  }

  try {
	// DB에서 해당 사용자가 존재하는지 조회
    const result = await User.findOne({where: {username : newUser.username}});

    let isJoined = '';
    if (result == null) {
	  // 비밀번호 암호화
	  const hashPwd = ;
	  
      await User.create(newUser); // 새 회원 등록
      
      isJoined = 'success';
      // 회원가입 완료 메시지를 전송
      res.render("join.ejs", {isJoined : {message : isJoined}});
      
    } else {
      isJoined = 'fail';
      // 회원가입 실패 메시지를 전송
      res.render("join.ejs", {isJoined : {message : isJoined}});
    }
  } catch (error) {
    res.redirect("/error");
  }
});

로그아웃

// 로그아웃
app.get('/logout', (req, res)=>{
    req.logout(()=>{
      res.redirect('/login');
  });
});